/**
 * @file Graphics.cpp
 *
 * @date 25/12/2011
 * @author Tales 
 *
 */

#include "Graphics.hpp"
#include "common.hpp"
#include "Color.hpp"
#include "errors.hpp"
#include <stack>
#include <cstring>

namespace wolf {

Graphics::Graphics(SDL_Surface *surface_, bool own) {
	WOLF_ASSERT(surface_ != NULL);
	useAlpha = true;
	surface = surface_;
	if (!own)
		surface->refcount++;
}
Graphics::~Graphics() {
	SDL_FreeSurface(surface);
}

void Graphics::putPixelRaw(Sint16 x, Sint16 y, Uint32 pixel) {
	int bpp = surface->format->BytesPerPixel;
	/* Here p is the address to the pixel we want to set */
	Uint8 *p = (Uint8*) ((((((surface->pixels)))))) + y * surface->pitch
			+ x * bpp;
	switch (bpp) {
	case 1:
		*p = pixel;
		break;

	case 2:
		*(Uint16 *) p = pixel;
		break;

	case 3:
		if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
			p[0] = (pixel >> 16) & 0xff;
			p[1] = (pixel >> 8) & 0xff;
			p[2] = pixel & 0xff;
		} else {
			p[0] = pixel & 0xff;
			p[1] = (pixel >> 8) & 0xff;
			p[2] = (pixel >> 16) & 0xff;
		}
		break;

	case 4:
		*(Uint32 *) p = pixel;
		break;
	}
}

Uint32 Graphics::getRawColor() const {
	return rawColor;
}

SDL_Surface *Graphics::getSurface() const {
	return surface;
}

void Graphics::setRawColor(Uint32 rawColor) {
	this->rawColor = rawColor;
}

void Graphics::setSurface(SDL_Surface *surface, bool own) {
	if (surface == this->surface)
		return;

	SDL_FreeSurface(this->surface);
	this->surface = surface;
	if (!own) {
		++this->surface->refcount;
	}
}

void Graphics::putPixel(Sint16 x, Sint16 y) {
	SDL_Rect & rect = surface->clip_rect;
	if (x >= rect.x && x < rect.w && y >= rect.y && y < rect.h)
		putPixelRaw(x, y, rawColor);

}
Color Graphics::getColor() {
	return Color(rawColor, surface->format);
}

void Graphics::setColor(const Color & color) {
	rawColor = color.toPixel(surface->format);
}

void Graphics::drawPixel(const pair<int, int> &pos) {
	if (SDL_MUSTLOCK(surface))
		SDL_LockSurface(surface);
	putPixel(pos.first, pos.second);
	if (SDL_MUSTLOCK(surface))
		SDL_UnlockSurface(surface);
}

void Graphics::drawLine(const pair<int, int> &p1, const pair<int, int> &p2) {
	if (SDL_MUSTLOCK(surface))
		SDL_LockSurface(surface);
	//The algorithm
	int x = p1.first;
	int y = p1.second;
	int deltaX = p2.first - p1.first;
	int deltaY = p2.second - p1.second;
	if (deltaX < 0) {
		//        drawLine2(-x, y, -deltaX, deltaY, TRANSF_H);
		x = -x;
		deltaX = -deltaX;
		if (deltaY < 0) {
			//            drawLine3( x, -y, deltaX, -deltaY, transf | TRANSF_V);
			y = -y;
			deltaY = -deltaY;
			if (deltaX < deltaY) {
				//                drawLine4( y, x, deltaY, deltaX, transf | TRANSF_I);
				double m = (double) ((deltaX)) / deltaY;
				double e = m - 1.0 / 2.0;
				for (int i = 0; i <= deltaY; ++i) {
					putPixel(-x, -y);
					while (e >= 0) {
						x = x + 1;
						e = e - 1;
					}
					y = y + 1;
					e = e + m;
				}
			} else {
				//                drawLine4( x, y, deltaX, deltaY, transf);
				double m = (double) ((deltaY)) / deltaX;
				double e = m - 1.0 / 2.0;
				for (int i = 0; i <= deltaX; ++i) {
					putPixel(-x, -y);
					while (e >= 0) {
						y = y + 1;
						e = e - 1;
					}
					x = x + 1;
					e = e + m;
				}
			}
		} else {
			//            drawLine3( x, y, deltaX, deltaY, transf);
			if (deltaX < deltaY) {
				//                drawLine4( y, x, deltaY, deltaX, transf | TRANSF_I);
				double m = (double) ((deltaX)) / deltaY;
				double e = m - 1.0 / 2.0;
				for (int i = 0; i <= deltaY; ++i) {
					putPixel(-x, y);
					while (e >= 0) {
						x = x + 1;
						e = e - 1;
					}
					y = y + 1;
					e = e + m;
				}
			} else {
				//                drawLine4( x, y, deltaX, deltaY, transf);
				double m = (double) ((deltaY)) / deltaX;
				double e = m - 1.0 / 2.0;
				for (int i = 0; i <= deltaX; ++i) {
					putPixel(-x, y);
					while (e >= 0) {
						y = y + 1;
						e = e - 1;
					}
					x = x + 1;
					e = e + m;
				}
			}
		}
	} else {
		//      drawLine2( x, y, deltaX, deltaY, 0);
		if (deltaY < 0) {
			//            drawLine3( x, -y, deltaX, -deltaY, transf | TRANSF_V);
			y = -y;
			deltaY = -deltaY;
			if (deltaX < deltaY) {
				//                drawLine4( y, x, deltaY, deltaX, transf | TRANSF_I);
				double m = (double) ((deltaX)) / deltaY;
				double e = m - 1.0 / 2.0;
				for (int i = 0; i <= deltaY; ++i) {
					putPixel(x, -y);
					while (e >= 0) {
						x = x + 1;
						e = e - 1;
					}
					y = y + 1;
					e = e + m;
				}

			} else {
				//                drawLine4( x, y, deltaX, deltaY, transf);
				double m = (double) ((deltaY)) / deltaX;
				double e = m - 1.0 / 2.0;
				for (int i = 0; i <= deltaX; ++i) {
					putPixel(x, -y);
					while (e >= 0) {
						y = y + 1;
						e = e - 1;
					}
					x = x + 1;
					e = e + m;
				}

			}

		} else {
			//            drawLine3( x, y, deltaX, deltaY, transf);
			if (deltaX < deltaY) {
				//                drawLine4( y, x, deltaY, deltaX, transf | TRANSF_I);
				double m = (double) ((deltaX)) / deltaY;
				double e = m - 1.0 / 2.0;
				for (int i = 0; i <= deltaY; ++i) {
					putPixel(x, y);
					while (e >= 0) {
						x = x + 1;
						e = e - 1;
					}
					y = y + 1;
					e = e + m;
				}
			} else {
				//                drawLine4( x, y, deltaX, deltaY, transf);
				double m = (double) ((deltaY)) / deltaX;
				double e = m - 1.0 / 2.0;
				for (int i = 0; i <= deltaX; ++i) {
					putPixel(x, y);
					while (e >= 0) {
						y = y + 1;
						e = e - 1;
					}
					x = x + 1;
					e = e + m;
				}
			}
		}
	}
	if (SDL_MUSTLOCK(surface))
		SDL_UnlockSurface(surface);
}

void Graphics::drawRectangle(const pair<int, int> &p1
		, const pair<int, int> & p2) {
	if (SDL_MUSTLOCK(surface))
		SDL_LockSurface(surface);
	int x = min(p1.first, p2.first);
	int y = min(p1.second, p2.second);
	int x2 = max(p1.first, p2.first);
	int y2 = max(p1.second, p2.second);
	for (int i = x; i <= x2; ++i) {
		putPixel(i, y);
		putPixel(i, y2);
	}
	for (int i = y; i <= y2; ++i) {
		putPixel(x, i);
		putPixel(x2, i);
	}
	if (SDL_MUSTLOCK(surface))
		SDL_UnlockSurface(surface);
}

void Graphics::fillRectangle(const pair<int, int> &p1
		, const pair<int, int> & p2) {
	if (SDL_MUSTLOCK(surface))
		SDL_LockSurface(surface);
	int x = min(p1.first, p2.first);
	int y = min(p1.second, p2.second);
	int x2 = max(p1.first, p2.first);
	int y2 = max(p1.second, p2.second);
	for (int i = y; i <= y2; ++i)
		for (int j = x; j <= x2; ++j)
			putPixel(j, i);

	if (SDL_MUSTLOCK(surface))
		SDL_UnlockSurface(surface);
}
template<class T> inline T square(T value) {
	return value * value;
}

void Graphics::drawEllipsis(const pair<int, int> &v1
		, const pair<int, int> & v2) {
	if (v1.first == v2.first || v1.second == v2.second)
		return;
	if (SDL_MUSTLOCK(surface))
		SDL_LockSurface(surface);
	int xC = (v1.first + v2.first) / 2;
	int yC = (v1.second + v2.second) / 2;
	double xRadius = abs(v1.first - v2.first) / 2;
	if (xRadius == 0)
		xRadius = 0.5;
	double yRadius = abs(v1.second - v2.second) / 2;
	if (yRadius == 0)
		yRadius = 0.5;
	int x = 0, y = yRadius;
	double p1 = (double) ((square(yRadius))) - square(xRadius) * yRadius
			+ 0.25 * square(xRadius);
	while (square(yRadius) * x <= square(xRadius) * y) {
		int maxX = xC + x;
		int minX = xC - x;
		int maxY = yC + y;
		int minY = yC - y;
		putPixel(minX, minY);
		putPixel(minX, maxY);
		putPixel(maxX, minY);
		putPixel(maxX, maxY);
		++x;
		if (p1 < 0)
			p1 += 2 * square(yRadius) * x + square(yRadius);

		else {
			--y;
			p1 += 2 * square(yRadius) * x - 2 * square(xRadius) * y
					+ square(yRadius);
		}
	}

	x = xRadius, y = 0;
	double p2 = (double) ((square(xRadius))) - square(yRadius) * xRadius
			+ 0.25 * square(yRadius);
	while (square(yRadius) * x >= square(xRadius) * y) {
		int maxX = xC + x;
		int minX = xC - x;
		int maxY = yC + y;
		int minY = yC - y;
		putPixel(minX, minY);
		putPixel(minX, maxY);
		putPixel(maxX, minY);
		putPixel(maxX, maxY);
		++y;
		if (p2 < 0)
			p2 += 2 * square(xRadius) * y + square(xRadius);

		else {
			--x;
			p2 += 2 * square(xRadius) * y - 2 * square(yRadius) * x
					+ square(xRadius);
		}
	}

	if (SDL_MUSTLOCK(surface))
		SDL_UnlockSurface(surface);
}

void Graphics::fillEllipsis(const pair<int, int> &v1
		, const pair<int, int> & v2) {
	if (v1.first == v2.first || v1.second == v2.second)
		return;
	if (SDL_MUSTLOCK(surface))
		SDL_LockSurface(surface);
	int xC = (v1.first + v2.first) / 2;
	int yC = (v1.second + v2.second) / 2;
	double xRadius = abs(v1.first - v2.first) / 2;
	if (xRadius == 0)
		xRadius = 0.5;
	double yRadius = abs(v1.second - v2.second) / 2;
	if (yRadius == 0)
		yRadius = 0.5;
	int x = 0, y = yRadius;
	double p1 = (double) ((square(yRadius))) - square(xRadius) * yRadius
			+ 0.25 * square(xRadius);
	while (square(yRadius) * x <= square(xRadius) * y) {
		int maxX = xC + x;
		int minX = xC - x;
		int maxY = yC + y;
		int minY = yC - y;
		for (int i = minX; i < maxX; ++i) {
			putPixel(i, minY);
			putPixel(i, maxY);
		}
		++x;
		if (p1 < 0)
			p1 += 2 * square(yRadius) * x + square(yRadius);
		else {
			--y;
			p1 += 2 * square(yRadius) * x - 2 * square(xRadius) * y
					+ square(yRadius);
		}
	}

	x = xRadius, y = 0;
	double p2 = (double) ((square(xRadius))) - square(yRadius) * xRadius
			+ 0.25 * square(yRadius);
	while (square(yRadius) * x >= square(xRadius) * y) {
		int maxX = xC + x;
		int minX = xC - x;
		int maxY = yC + y;
		int minY = yC - y;
		for (int i = minX; i < maxX; ++i) {
			putPixel(i, minY);
			putPixel(i, maxY);
		}
		++y;
		if (p2 < 0)
			p2 += 2 * square(xRadius) * y + square(xRadius);

		else {
			--x;
			p2 += 2 * square(xRadius) * y - 2 * square(yRadius) * x
					+ square(xRadius);
		}
	}

	if (SDL_MUSTLOCK(surface))
		SDL_UnlockSurface(surface);
}

void Graphics::blit(const Graphics &src, const pair<int, int> &position) {
	SDL_Rect posRect = { (Sint16) ((position.first)),
			(Sint16) ((position.second)) };
	SDL_BlitSurface(src.surface, NULL, surface, &posRect);
}

void Graphics::blit(const Graphics &src, const pair<int, int> &position
		, SDL_Rect slice) {
	SDL_Rect posRect = { (Sint16) position.first, (Sint16) position.second };
	SDL_BlitSurface(src.surface, &slice, surface, &posRect);
}

SDL_Surface *Graphics::clone() {
	return SDL_ConvertSurface(surface, surface->format, surface->flags);
}

SDL_Surface *Graphics::clone(SDL_Rect slice) {
	//Verifica se haverá área > 0
	if (slice.x >= surface->w || slice.y >= surface->h || slice.w == 0
			|| slice.h == 0)
		return NULL;
	//Ajusta a area para não ultrapassar os limites na horizontal.
	if (slice.w + slice.x > surface->w)
		slice.w = surface->w - slice.x;
	//Ajusta a área para não ultrapassar os limites na vertical.
	if (slice.h + slice.y > surface->h)
		slice.h = surface->h - slice.y;

	//Cria uma nova superfície com as mesmas características da original
	SDL_Surface *cloned = SDL_CreateRGBSurface(surface->flags, slice.w, slice.h,
			surface->format->BitsPerPixel, surface->format->Rmask,
			surface->format->Gmask, surface->format->Bmask,
			surface->format->Amask);
	if (!cloned)
		throw SdlError(SDL_GetError());
//	YEP_ERROR_TEST(cloned != NULL, "Can not create a surface");
	//Copia a cor transparente
	if (surface->flags & SDL_SRCCOLORKEY) {
		SDL_SetColorKey(cloned,
				surface->flags & (SDL_SRCCOLORKEY | SDL_RLEACCEL),
				surface->format->colorkey);
	}
	if (SDL_MUSTLOCK(surface))
		SDL_LockSurface(surface);
	if (SDL_MUSTLOCK(cloned))
		SDL_LockSurface(cloned);
	int bpp = surface->format->BytesPerPixel;
	//TODO: Remove this workarround:
//	Graphics gClone(cloned);
	for (int y = 0; y < slice.h; ++y) {
		Uint8 *pSrc = (Uint8*) surface->pixels + (y + slice.y) * surface->pitch
				+ slice.x * bpp;
		Uint8 *pDst = (Uint8*) cloned->pixels + y * cloned->pitch;
		memcpy(pDst, pSrc, cloned->pitch);
//		for(int x = 0; x < slice.w; ++x)
//		{
//			Uint32 pixel = getPixelRaw(x + slice.x, y + slice.y);
//			gClone.putPixelRaw(x, y, pixel);
//		}
	}
	if (SDL_MUSTLOCK(cloned))
		SDL_UnlockSurface(cloned);
	if (SDL_MUSTLOCK(surface))
		SDL_UnlockSurface(surface);
	return cloned;
}

Uint32 Graphics::getPixelRaw(Sint16 x, Sint16 y) {
	int bpp = surface->format->BytesPerPixel;

	if (SDL_MUSTLOCK(surface))
		SDL_LockSurface(surface);
	/* Here p is the address to the pixel we want to retrieve */
	Uint8 *p = (Uint8*) (((surface->pixels))) + y * surface->pitch + x * bpp;

	if (SDL_MUSTLOCK(surface))
		SDL_UnlockSurface(surface);
	switch (bpp) {
	case 1:
		return *p;

	case 2:
		return *(Uint16 *) p;

	case 3:
		if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
			return p[0] << 16 | p[1] << 8 | p[2];
		else
			return p[0] | p[1] << 8 | p[2] << 16;

	case 4:
		return *(Uint32 *) p;

	default:
		return 0; /* shouldn't happen, but avoids warnings */
	}
}

Color Graphics::getPixel(Sint16 x, Sint16 y) {
	return Color(getPixelRaw(x, y), surface->format);
}

void Graphics::floodFill(const pair<int, int> &pos) {
	Uint32 oldColor = getPixelRaw(pos.first, pos.second);
	if (rawColor == oldColor)
		return;

	stack<pair<int, int> > pointStack;
	pointStack.push(pos);
	while (!pointStack.empty()) {
		pair<int, int> actual = pointStack.top();
		pointStack.pop();
		if (getPixelRaw(actual.first, actual.second) == oldColor) {
			putPixel(actual.first, actual.second);
			if (actual.first > 0)
				pointStack.push(make_pair(actual.first - 1, actual.second));

			if (actual.first < surface->w - 1)
				pointStack.push(make_pair(actual.first + 1, actual.second));

			if (actual.second > 0)
				pointStack.push(make_pair(actual.first, actual.second - 1));

			if (actual.second < surface->h - 1)
				pointStack.push(make_pair(actual.first, actual.second + 1));

		}
	}

}

}/* namespace wolf */

